package com.fandou.airobot.service;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.os.Bundle;
import android.util.SparseBooleanArray;

import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;

import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.TimeUtils;
import com.fandou.airobot.R;
import com.fandou.airobot.database.Alarm;
import com.fandou.airobot.database.AlarmConstants;
import com.fandou.airobot.database.AlarmDatabase;
import com.fandou.airobot.ui.alarm.AlarmEvent;
import com.fandou.airobot.ui.alarm.AlarmUtils;
import com.fandou.airobot.ui.alarm.RemindActivity;
import com.google.gson.Gson;
import com.jeremyliao.liveeventbus.LiveEventBus;

import java.util.Calendar;
import java.util.List;
import java.util.Objects;

import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;


/**
 * @author Jm
 * @date 2020/7/1 10:45
 * @description The alarm receiver
 */
public final class AlarmReceiver extends BroadcastReceiver {

    private static final String TAG = AlarmReceiver.class.getSimpleName();

    private static final String CHANNEL_ID = "alarm_channel";

    private static final String BUNDLE_EXTRA = "bundle_extra";

    private static final String ALARM_KEY = "alarm_key";

    @Override
    public void onReceive(Context context, Intent intent) {

        final Alarm alarm = Objects.requireNonNull(intent.getBundleExtra(BUNDLE_EXTRA)).getParcelable(ALARM_KEY);
        if (alarm == null) {
            LogUtils.eTag(TAG, "Alarm is null", new NullPointerException());
            return;
        }
        LogUtils.dTag(TAG, "onReceive alarm===============" + new Gson().toJson(alarm));

        //这里处理一次性闹钟，当如果是一次性闹钟，并且闹钟时间已经过去了，
        //则把这个闹钟设置为不可用。
        if (!alarm.repeat && alarm.time < TimeUtils.getNowMills()) {
            LogUtils.dTag(TAG, "一次性闹钟==响了=============" + new Gson().toJson(alarm));
            alarm.setEnable(false);
            AlarmDatabase.getInstance().alarmDao().updateAlarm(alarm);
            //通知闹钟列表更新这个alarm
            LiveEventBus.get(AlarmEvent.UPDATE).post(new AlarmEvent(alarm));
            //cancel the once alarm
            cancelReminderAlarm(context, alarm);
        }

        assert alarm.remark != null;
        PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(context, 0,
                RemindActivity.getRemindIntent(context, alarm), PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_sound_on)
                .setContentTitle("闹铃提醒")
                .setContentText(alarm.getRemark())
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setVibrate(new long[]{1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 500, 1000, 500})

                .setAutoCancel(true)
                .setCategory(NotificationCompat.CATEGORY_ALARM)

                // Use a full-screen intent only for the highest-priority alerts where you
                // have an associated activity that you would like to launch after the user
                // interacts with the notification. Also, if your app targets Android 10
                // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
                // order for the platform to invoke this notification.
                .setFullScreenIntent(fullScreenPendingIntent, true);


        //如果8.0以上包括8.0 ,setSound是无效的
        if (SDK_INT > O) {
            //
        }
        //8.0以下的 声音 开关设置
        else {
            if (alarm.alertType == AlarmConstants.RemindType.ALERT_SOUND) {
                notificationBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
            } else {
                notificationBuilder.setSound(null);
            }
        }

        if (alarm.alertType == AlarmConstants.RemindType.ALERT_SOUND) {
            notificationBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE));
        } else {
            notificationBuilder.setSound(null);
        }

        final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        createNotificationChannel(context);
        Notification incomingCallNotification = notificationBuilder.build();
        manager.notify((int) alarm.getId(), incomingCallNotification);

        //Reset Alarm manually
        setReminderAlarm(context, alarm);
    }

    //Convenience method for setting a notification
    public static void setReminderAlarm(Context context, Alarm alarm) {
        //Check whether the alarm is set to run on any days
        if (!AlarmUtils.isAlarmActive(alarm)) {
            //If alarm not set to run on any days, cancel any existing notifications for this alarm
            cancelReminderAlarm(context, alarm);
            return;
        }

        LogUtils.dTag(TAG, "设置闹钟===============" + new Gson().toJson(alarm));

        final Calendar nextAlarmTime = getTimeForNextAlarm(alarm);
        alarm.setTime(nextAlarmTime.getTimeInMillis());
        final Intent intent = new Intent(context, AlarmReceiver.class);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(ALARM_KEY, alarm);
        intent.putExtra(BUNDLE_EXTRA, bundle);

        final PendingIntent pIntent = PendingIntent.getBroadcast(
                context,
                (int) alarm.getId(),
                intent,
                FLAG_UPDATE_CURRENT
        );

        ScheduleAlarm.with(context).schedule(alarm, pIntent);
    }

    /**
     * Set all the alarms when enable or cancel alarms when unEnable
     *
     * @param context context
     * @param alarms  alarms
     */
    public static void setReminderAlarms(Context context, List<Alarm> alarms) {
        for (Alarm alarm : alarms) {
            if (alarm.enable) {
                LogUtils.dTag(TAG, "Alarm setting：" + alarm.id + ",remark:" + alarm.remark);
                setReminderAlarm(context, alarm);
            } else {
                LogUtils.dTag(TAG, "Alarm cancel：" + alarm.id + ",remark:" + alarm.remark);
                cancelReminderAlarm(context, alarm);
            }
        }
    }

    /**
     * Calculates the actual time of the next alarm/notification based on the user-set time the
     * alarm should sound each day, the days the alarm is set to run, and the current time.
     *
     * @param alarm Alarm containing the daily time the alarm is set to run and days the alarm
     *              should run
     * @return A Calendar with the actual time of the next alarm.
     */
    private static Calendar getTimeForNextAlarm(Alarm alarm) {

        final Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(alarm.getTime());
        final long currentTime = System.currentTimeMillis();
        final int startIndex = getStartIndexFromTime(calendar);

        int count = 0;
        boolean isAlarmSetForDay;

        final SparseBooleanArray daysArray = alarm.getAllDays();

        do {
            final int index = (startIndex + count) % 7;
            isAlarmSetForDay = daysArray.valueAt(index) && (calendar.getTimeInMillis() > currentTime);
            if (!isAlarmSetForDay) {
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                count++;
            }
        } while (!isAlarmSetForDay && count < 7);

        return calendar;

    }

    /**
     * Cancel an alarm
     *
     * @param context context
     * @param alarm   Alarm
     */
    public static void cancelReminderAlarm(Context context, Alarm alarm) {
        final Intent intent = new Intent(context, AlarmReceiver.class);
        final PendingIntent pIntent = PendingIntent.getBroadcast(
                context,
                (int) alarm.getId(),
                intent,
                FLAG_UPDATE_CURRENT
        );

        final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        manager.cancel(pIntent);
    }

    private static int getStartIndexFromTime(Calendar c) {

        final int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);

        int startIndex = 0;
        switch (dayOfWeek) {
            case Calendar.MONDAY:
                startIndex = 0;
                break;
            case Calendar.TUESDAY:
                startIndex = 1;
                break;
            case Calendar.WEDNESDAY:
                startIndex = 2;
                break;
            case Calendar.THURSDAY:
                startIndex = 3;
                break;
            case Calendar.FRIDAY:
                startIndex = 4;
                break;
            case Calendar.SATURDAY:
                startIndex = 5;
                break;
            case Calendar.SUNDAY:
                startIndex = 6;
                break;
        }

        return startIndex;
    }

    /**
     * 为android 8.0以以上创建一个通知通道
     *
     * @param context context
     */
    private static void createNotificationChannel(Context context) {
        if (SDK_INT < O) return;
        final NotificationManager mgr = context.getSystemService(NotificationManager.class);
        if (mgr == null) return;

        final String name = context.getString(R.string.channel_name);
        if (mgr.getNotificationChannel(name) == null) {
            final NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
            channel.enableVibration(true);//是否震动
            channel.setVibrationPattern(new long[]{1000, 500, 1000, 500, 1000, 500});
            channel.setBypassDnd(true);//是否绕过请勿打扰模式
            mgr.createNotificationChannel(channel);
        }
    }

    private static PendingIntent launchAlarmLandingPage(Context context, Alarm alarm) {
        return PendingIntent.getActivity(context, (int) alarm.getId(), RemindActivity.getRemindIntent(context, alarm), FLAG_UPDATE_CURRENT
        );
    }

    private static class ScheduleAlarm {

        @NonNull
        private final Context context;
        @NonNull
        private final AlarmManager am;

        private ScheduleAlarm(@NonNull AlarmManager am, @NonNull Context context) {
            this.am = am;
            this.context = context;
        }

        static ScheduleAlarm with(Context context) {
            final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            if (am == null) {
                throw new IllegalStateException("AlarmManager is null");
            }
            return new ScheduleAlarm(am, context);
        }

        @SuppressLint("ObsoleteSdkInt")
        void schedule(Alarm alarm, PendingIntent pi) {
            if (SDK_INT > LOLLIPOP) {
                am.setAlarmClock(new AlarmClockInfo(alarm.getTime(), launchAlarmLandingPage(context, alarm)), pi);
            } else if (SDK_INT > KITKAT) {
                am.setExact(AlarmManager.RTC_WAKEUP, alarm.getTime(), pi);
            } else {
                am.set(AlarmManager.RTC_WAKEUP, alarm.getTime(), pi);
            }
        }

    }

}
